using System;
using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Classification.Interfaces;
using Ewk.Math.Algebra;

namespace Ewk.MachineLearning.Classification.Conversions
{
    /// <summary>
    /// Contains extension methods for converting <see cref="ILinkedEntity"/>
    /// to <see cref="Vector{T}"/> where T is of type <see cref="double"/>.
    /// </summary>
    public static class LinkedEntityToVectorConversions
    {
        /// <summary>
        /// Converts the specified <see cref="IEnumerable{T}"/> with T of type <see cref="ILinkedEntity"/>
        /// to an <see cref="IEnumerable{T}"/> with T of type <see cref="Vector{TElement}"/> where TElement is of type <see cref="double"/>.
        /// </summary>
        /// <typeparam name="TClassification">The <see cref="Type"/> of the entity.</typeparam>
        /// <typeparam name="TFeature">The <see cref="Type"/> of the linked detail entity.</typeparam>
        /// <param name="classifications">The <see cref="ILinkedEntity"/> instances to convert.</param>
        /// <param name="entities">The dimension of the result <see cref="Vector{T}"/>.</param>
        /// <returns>
        /// An <see cref="IEnumerable{T}"/> with T of type <see cref="Vector{TElement}"/> where TElement is of type <see cref="double"/>
        /// that represents the specified <see cref="IEnumerable{T}"/> with T of type <see cref="ILinkedEntity"/>.
        /// </returns>
        public static IEnumerable<Vector<double>> ToVectors<TClassification, TFeature>(
            this IEnumerable<TClassification> classifications, List<IEntity> entities)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
        {
            return classifications.Select(classification => 
                classification.ToVector<TClassification, TFeature>(entities));
        }

        /// <summary>
        /// Converts the specified <see cref="ILinkedEntity"/> to a <see cref="Vector{TElement}"/>
        /// where TElement is of type <see cref="double"/>.
        /// </summary>
        /// <typeparam name="TClassification">The <see cref="Type"/> of the entity.</typeparam>
        /// <typeparam name="TFeature">The <see cref="Type"/> of the linked detail entity.</typeparam>
        /// <param name="classification">The <see cref="ILinkedEntity"/> instance to convert.</param>
        /// <param name="entities">The dimension of the result <see cref="Vector{T}"/>.</param>
        /// <returns>
        /// A <see cref="Vector{TElement}"/> where TElement is of type <see cref="double"/> that represents
        /// the specified <see cref="ILinkedEntity"/>.
        /// </returns>
        public static Vector<double> ToVector<TClassification, TFeature>(
            this TClassification classification, List<IEntity> entities)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
        {
            var values = new double[entities.Count];

            classification
                .IterateLinks<TClassification, TFeature>(entityLink =>
                                                         values[
                                                             entities
                                                                 .FindIndex(entity =>
                                                                            entity.Id == entityLink.FeatureId)
                                                             ] += entityLink.Occurrence);

            return new Vector<double>(values);
        }

        /// <summary>
        /// Converts the specified <see cref="IEnumerable{T}"/> with T of type <see cref="ILinkedEntity"/>
        /// to a <see cref="Vector{TElement}"/> where TElement is of type <see cref="double"/>.
        /// </summary>
        /// <typeparam name="TClassification">The <see cref="Type"/> of the entity.</typeparam>
        /// <typeparam name="TFeature">The <see cref="Type"/> of the linked detail entity.</typeparam>
        /// <param name="classifications">The <see cref="ILinkedEntity"/> instances to convert.</param>
        /// <param name="entities">The dimension of the result <see cref="Vector{T}"/>.</param>
        /// <returns>
        /// A <see cref="Vector{TElement}"/> where TElement is of type <see cref="double"/>
        /// that represents the specified <see cref="IEnumerable{T}"/> with T of type <see cref="ILinkedEntity"/>.
        /// </returns>
        public static Vector<double> ToVector<TClassification, TFeature>(
            this IEnumerable<TClassification> classifications, List<IEntity> entities)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
        {
            var values = new double[entities.Count];

            foreach (var linkedEntity in classifications)
            {
                linkedEntity
                    .IterateLinks<TClassification, TFeature>(entityLink =>
                                                             values[
                                                                 entities
                                                                     .FindIndex(entity =>
                                                                                entity.Id == entityLink.FeatureId)
                                                                 ] += entityLink.Occurrence);
            }

            return new Vector<double>(values);
        }

        /// <summary>
        /// Iterates through all <see cref="IFeatureLink"/> instances that are
        /// linked to the specified <see cref="ILinkedEntity"/>.
        /// </summary>
        /// <typeparam name="TClassification">The <see cref="Type"/> of the entity.</typeparam>
        /// <typeparam name="TFeature">The <see cref="Type"/> of the linked detail entity.</typeparam>
        /// <param name="linkedEntity">The <see cref="ILinkedEntity"/> instance that contains the links to iterate through.</param>
        /// <param name="action">The <see cref="Action{T}"/> to perform for each iteration.</param>
        public static void IterateLinks<TClassification, TFeature>(
            this TClassification linkedEntity, Action<IFeatureLink> action)
            where TClassification : ILinkedEntity
            where TFeature : IFeature
        {
            foreach (var entityLink in linkedEntity.Links)
            {
                action(entityLink);
            }
        }
    }
}